package com.wm.thread;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ClassName: JUC
 * @Description: 线程相关的题
 * @Author: WM
 * @Date: 2023/8/13 10:35
 */
public class JUC {
    public static void main(String[] args) {
        printTurn();
    }

    /**
     * 1115. 交替打印 FooBar
     * https://leetcode.cn/problems/print-foobar-alternately/
     * 方法：信号量Semaphore
     * 思路：定义两个信号量，一个代表first方法获取释放许可的信号量，一个代表second方法。在执行run方法之前，
     * first信号量获取许可，run运行之后，second信号量释放许可。另一个方法同理。
     */
    class FooBar {
        private int n;

        Semaphore fooSemaphore = new Semaphore(1);
        Semaphore barSemaphore = new Semaphore(0);

        public FooBar(int n) {
            this.n = n;
        }


        public void foo(Runnable printFoo) throws InterruptedException {

            for (int i = 0; i < n; i++) {
                fooSemaphore.acquire();
                printFoo.run();
                barSemaphore.release();
            }
        }

        public void bar(Runnable printBar) throws InterruptedException {

            for (int i = 0; i < n; i++) {
                barSemaphore.acquire();
                printBar.run();
                fooSemaphore.release();
            }
        }
    }

    /**
     * 1114. 按序打印
     * https://leetcode.cn/problems/print-in-order/description/
     * 方法4：Semaphore
     * 思路：使用Semaphore避免空循环消耗带来的消耗CPU
     */
    class Foo3 {
        Semaphore semaphore1;
        Semaphore semaphore2;

        public Foo3() {
            // 0就是没有资源可以获取，只能阻塞，当有别的线程调用release时，才会给信号量加一个资源
            semaphore1 = new Semaphore(0);
            semaphore2 = new Semaphore(0);
        }


        public void first(Runnable printFirst) throws InterruptedException {

            printFirst.run();
            semaphore1.release();
        }

        public void second(Runnable printSecond) throws InterruptedException {
            semaphore1.acquire();
            printSecond.run();
            semaphore2.release();
        }

        public void third(Runnable printThird) throws InterruptedException {
            semaphore2.acquire();
            printThird.run();
        }
    }

    /**
     * 1114. 按序打印
     * https://leetcode.cn/problems/print-in-order/description/
     * 方法3：CountDownLatch
     * 思路：使用CountDownLatch避免空循环消耗带来的消耗CPU
     */
    class Foo2 {
        CountDownLatch countDownLatch1;
        CountDownLatch countDownLatch2;

        public Foo2() {
            countDownLatch1 = new CountDownLatch(1);
            countDownLatch2 = new CountDownLatch(1);
        }


        public void first(Runnable printFirst) throws InterruptedException {

            printFirst.run();
            countDownLatch1.countDown();
        }

        public void second(Runnable printSecond) throws InterruptedException {
            countDownLatch1.await();
            printSecond.run();
            countDownLatch2.countDown();
        }

        public void third(Runnable printThird) throws InterruptedException {
            countDownLatch2.await();
            printThird.run();
        }
    }

    /**
     * 1114. 按序打印
     * https://leetcode.cn/problems/print-in-order/description/
     * 方法2：CountDownLatch
     * 思路：使用CountDownLatch会有空循环消耗带来的消耗CPU
     */
    class Foo21 {
        CountDownLatch countDownLatch;

        public Foo21() {
            countDownLatch = new CountDownLatch(3);
        }


        public void first(Runnable printFirst) throws InterruptedException {

            printFirst.run();
            countDownLatch.countDown();
        }

        public void second(Runnable printSecond) throws InterruptedException {
            while (countDownLatch.getCount() != 2) {

            }
            printSecond.run();
            countDownLatch.countDown();
        }

        public void third(Runnable printThird) throws InterruptedException {
            while (countDownLatch.getCount() != 1) {

            }
            printThird.run();
            countDownLatch.countDown();
        }
    }

    /**
     * 1114. 按序打印
     * https://leetcode.cn/problems/print-in-order/description/
     * 方法1：原子整形类
     * 思路：官方给出的是使用原子操作类AtomicInteger，定义两个原子操作类，如果第一个方法执行完+1，
     * 第二个方法判断第一个原子操作类的变量，该变量为1表示，上一个方法执行完成了，否则就在这空转，当然这个
     * 空循环是会消耗CPU的。
     */
    class Foo {

        AtomicInteger firstJobDone;
        AtomicInteger secondJobDone;

        public Foo() {
            firstJobDone = new AtomicInteger(0);
            secondJobDone = new AtomicInteger(0);
        }


        public void first(Runnable printFirst) throws InterruptedException {

            printFirst.run();
            firstJobDone.incrementAndGet();
        }

        public void second(Runnable printSecond) throws InterruptedException {
            while (firstJobDone.get() != 1) {
            }
            printSecond.run();
            secondJobDone.incrementAndGet();
        }

        public void third(Runnable printThird) throws InterruptedException {
            while (secondJobDone.get() != 1) {
            }
            printThird.run();
        }
    }

    /**
     * 两个线程交替打印两个数组中的元素 | 多个线程按顺序输出数字
     * 方法1：notify+wait方法
     */
    public static void printTurn() {
        char[] arr1 = {'1', '2', '3', '4', '5', '6', '7', '8', '9'};
        char[] arr2 = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'};
        Object o = new Object();
        new Thread(() -> {
            synchronized (o) {
                for (char c : arr1) {
                    System.out.print(c + "\t");
                    try {
                        o.notify();
                        o.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (o) {
                for (char c : arr2) {
                    System.out.print(c + "\t");
                    try {
                        o.notify();
                        o.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();
    }
}
